001 /* 002 * Copyright 2004-2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.transit.tools; 020 021 import java.io.InputStream; 022 import java.util.Vector; 023 import java.util.Hashtable; 024 import java.net.URI; 025 026 import net.dpml.lang.Part; 027 import net.dpml.lang.Plugin; 028 import net.dpml.lang.Resource; 029 030 import net.dpml.util.ElementHelper; 031 032 import org.apache.tools.ant.Project; 033 import org.apache.tools.ant.BuildException; 034 import org.apache.tools.ant.ComponentHelper; 035 import org.apache.tools.ant.SubBuildListener; 036 import org.apache.tools.ant.BuildEvent; 037 038 import org.w3c.dom.Element; 039 040 /** 041 * A component helper that handles automatic loading of plugins into the 042 * ant plugin based on namespace declarations in the project file. This is similar 043 * to the 'antlib:' convention except we use the 'plugin:' convention. 044 * 045 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 046 * @version 1.0.2 047 */ 048 public class TransitComponentHelper extends ComponentHelper 049 implements SubBuildListener 050 { 051 //------------------------------------------------------------------------ 052 // static 053 //------------------------------------------------------------------------ 054 055 /** 056 * The constant Transit ANTLIB namespace. 057 */ 058 public static final String TRANSIT_ANTLIB_URN = "antlib:net.dpml.transit"; 059 060 /** 061 * The constant Transit ANTLIB init task namespace. 062 */ 063 public static final String TRANSIT_INIT_URN = TRANSIT_ANTLIB_URN + ":init"; 064 065 /** 066 * The constant Transit ANTLIB plugin task namespace. 067 */ 068 public static final String TRANSIT_PLUGIN_URN = TRANSIT_ANTLIB_URN + ":plugin"; 069 070 /** 071 * The constant Transit ANTLIB import task namespace. 072 */ 073 public static final String TRANSIT_IMPORT_URN = TRANSIT_ANTLIB_URN + ":import"; 074 075 /** 076 * The constant Transit ANTLIB get task namespace. 077 */ 078 public static final String TRANSIT_GET_URN = TRANSIT_ANTLIB_URN + ":get"; 079 080 /** 081 * The constant artifact plugin header. 082 */ 083 public static final String PLUGIN_ARTIFACT_HEADER = "artifact:part:"; 084 085 /** 086 * Creation of a component helper for the supplied project. 087 * 088 * @param project the project 089 */ 090 public static void initialize( Project project ) 091 { 092 initialize( project, false ); 093 } 094 095 /** 096 * Creation of a component helper for the supplied project. 097 * 098 * @param project the project 099 * @param flag subproject flag 100 */ 101 public static void initialize( Project project, boolean flag ) 102 { 103 ComponentHelper current = 104 (ComponentHelper) project.getReference( "ant.ComponentHelper" ); 105 if( ( null != current ) && ( current instanceof TransitComponentHelper ) ) 106 { 107 return; 108 } 109 TransitComponentHelper helper = new TransitComponentHelper( project, current ); 110 helper.initDefaultDefinitions(); 111 if( flag ) 112 { 113 project.log( 114 "\nAssigning Transit component helper to sub-project: " 115 + project.getBaseDir() ); 116 } 117 else 118 { 119 project.log( 120 "\nAssigning Transit component helper to project: " 121 + project.getBaseDir() ); 122 } 123 project.addReference( "ant.ComponentHelper", helper ); 124 project.addBuildListener( helper ); 125 } 126 127 /** 128 * Vector of plugin uris already loaded. 129 */ 130 private static Vector m_URIS = new Vector(); 131 132 /** 133 * Table of urn to uri mappings. 134 */ 135 private static Hashtable m_MAPPINGS = new Hashtable(); 136 137 /** 138 * Register the mapping between a urn and a plugin uri. 139 * @param maps a sequence of urn to uri bindings 140 */ 141 public static void register( MapDataType[] maps ) 142 { 143 if( null == maps ) 144 { 145 throw new NullPointerException( "maps" ); 146 } 147 148 for( int i=0; i < maps.length; i++ ) 149 { 150 MapDataType map = maps[i]; 151 String urn = map.getURN(); 152 if( !m_MAPPINGS.contains( urn ) ) 153 { 154 URI uri = map.getURI(); 155 m_MAPPINGS.put( urn, uri ); 156 } 157 } 158 } 159 160 //------------------------------------------------------------------------ 161 // state 162 //------------------------------------------------------------------------ 163 164 /** 165 * The current project. 166 */ 167 private Project m_project; 168 169 /** 170 * The parent component helper. 171 */ 172 private ComponentHelper m_parent; 173 174 //------------------------------------------------------------------------ 175 // constructor 176 //------------------------------------------------------------------------ 177 178 /** 179 * Creation of a new transit component helper. 180 * @param project the current project 181 */ 182 public TransitComponentHelper( Project project ) 183 { 184 this( project, ComponentHelper.getComponentHelper( project ) ); 185 } 186 187 /** 188 * Creation of a new transit component helper. 189 * @param project the current project 190 * @param parent the parent component helper 191 */ 192 public TransitComponentHelper( Project project, ComponentHelper parent ) 193 { 194 setProject( project ); 195 m_parent = parent; 196 if( null != parent ) 197 { 198 parent.setNext( this ); 199 } 200 201 Hashtable map = getTaskDefinitions(); 202 if( null == map.get( TRANSIT_INIT_URN ) ) 203 { 204 addTaskDefinition( TRANSIT_INIT_URN, MainTask.class ); 205 addTaskDefinition( TRANSIT_PLUGIN_URN, PluginTask.class ); 206 addTaskDefinition( TRANSIT_IMPORT_URN, ImportArtifactTask.class ); 207 addTaskDefinition( TRANSIT_GET_URN, GetTask.class ); 208 } 209 } 210 211 //------------------------------------------------------------------------ 212 // implementation 213 //------------------------------------------------------------------------ 214 215 /** 216 * Set the current project. 217 * @param project the current ant project 218 */ 219 public void setProject( Project project ) 220 { 221 m_project = project; 222 super.setProject( project ); 223 } 224 225 /** 226 * Create an object for a component using a supplied name. The name 227 * is the fully qualified component name which allows us to intercept 228 * specific namespace qualifiers - in this case 'plugin:'. In the event of 229 * a plugin namespace we check to see if the plugin for that name is already 230 * loaded and it not we proceed with classic transit-based loading of the 231 * plugin and registration of plugin classes with the component helper. 232 * 233 * @param name the name of the component, if the component is in a namespace, the 234 * name is prefixed with the namespace uri and ":" 235 * @return the class if found or null if not. 236 */ 237 public Object createComponent( String name ) 238 { 239 Object object = super.createComponent( name ); 240 if( null != object ) 241 { 242 return object; 243 } 244 245 if( null != m_parent ) 246 { 247 object = m_parent.createComponent( name ); 248 if( null != object ) 249 { 250 return object; 251 } 252 } 253 254 // 255 // from here we need to validate - code has been worked over more than a 256 // few time ans I would not recommend it for flight control scenarios 257 // just yet 258 // 259 260 int k = name.lastIndexOf( ":" ); 261 if( k > 0 ) 262 { 263 String urn = name.substring( 0, k ); 264 String task = name.substring( k + 1 ); 265 URI uri = convertUrnToURI( urn ); 266 if( null != uri ) 267 { 268 installPlugin( uri, urn, task ); 269 object = super.createComponent( name ); 270 if( null != object ) 271 { 272 return object; 273 } 274 else 275 { 276 final String error = 277 "Mapped urn returned a null object."; 278 throw new BuildException( error ); 279 } 280 } 281 else 282 { 283 return super.createComponent( name ); 284 } 285 } 286 else 287 { 288 return super.createComponent( name ); 289 } 290 } 291 292 /** 293 * Convert a urn to a uri taking into account possible urn alias names. 294 * 295 * @param urn the urn to convert to a uri 296 * @return the converted uri 297 */ 298 private URI convertUrnToURI( String urn ) 299 { 300 URI uri = (URI) m_MAPPINGS.get( urn ); 301 if( null != uri ) 302 { 303 return uri; 304 } 305 if( urn.startsWith( PLUGIN_ARTIFACT_HEADER ) ) 306 { 307 return convertToURI( urn ); 308 } 309 else 310 { 311 return null; 312 } 313 } 314 315 /** 316 * The implementation will retrieve the plugin descriptor. If the descriptor 317 * declares a classname then the class will be loaded and assigned under 318 * name. If the classname is undefined and resource is defined, the 319 * implementation will attempt to locate an antlib definition at the resource 320 * location and will attempt to load all taskdef and typedef entries declared 321 * in the antlib. 322 * 323 * @param uri the plugin uri 324 * @param name the fully qualified component name 325 */ 326 private void installPlugin( URI uri, String urn, String name ) 327 { 328 final String label = uri + ":" + name; 329 if( null != getTaskDefinitions().get( label ) ) 330 { 331 return; 332 } 333 334 try 335 { 336 m_project.log( "installing: " + uri + " as " + urn ); 337 338 Part part = Part.load( uri ); 339 340 ClassLoader current = Thread.currentThread().getContextClassLoader(); 341 342 if( part instanceof Plugin ) 343 { 344 Plugin plugin = (Plugin) part; 345 Class clazz = plugin.getPluginClass(); 346 final String key = uri + ":" + name; 347 getProject().log( "installing single task plugin [" + key + "]", Project.MSG_VERBOSE ); 348 super.addTaskDefinition( key, clazz ); 349 } 350 else if( part instanceof Resource ) 351 { 352 Resource res = (Resource) part; 353 String resource = res.getPath(); 354 getProject().log( "installing antlib plugin [" + resource + "]", Project.MSG_VERBOSE ); 355 ClassLoader classloader = part.getClassLoader(); 356 InputStream input = classloader.getResourceAsStream( resource ); 357 if( null == input ) 358 { 359 final String error = 360 "Cannot load resource [" 361 + resource 362 + "] because it does not exist within the cloassloader defined by the uri [" 363 + uri 364 + "]" 365 + "\n" + classloader.toString(); 366 throw new BuildException( error ); 367 } 368 369 Element root = ElementHelper.getRootElement( input ); 370 Element[] tasks = ElementHelper.getChildren( root, "taskdef" ); 371 for( int i=0; i < tasks.length; i++ ) 372 { 373 Element task = tasks[i]; 374 String key = urn + ":" + ElementHelper.getAttribute( task, "name" ); 375 getProject().log( "installing task [" + key + "]", Project.MSG_VERBOSE ); 376 String classname = ElementHelper.getAttribute( task, "classname" ); 377 Class clazz = classloader.loadClass( classname ); 378 super.addTaskDefinition( key, clazz ); 379 } 380 Element[] types = ElementHelper.getChildren( root, "typedef" ); 381 for( int i=0; i < types.length; i++ ) 382 { 383 Element type = types[i]; 384 String key = urn + ":" + ElementHelper.getAttribute( type, "name" ); 385 getProject().log( "installing type [" + key + "]", Project.MSG_VERBOSE ); 386 String classname = ElementHelper.getAttribute( type, "classname" ); 387 Class clazz = classloader.loadClass( classname ); 388 super.addDataTypeDefinition( key, clazz ); 389 } 390 } 391 m_URIS.add( uri ); 392 } 393 catch( BuildException e ) 394 { 395 throw e; 396 } 397 catch( Throwable e ) 398 { 399 final String error = 400 "Could not load plugin: " + uri; 401 throw new BuildException( error, e ); 402 } 403 } 404 405 /** 406 * Returns the current project. 407 * @return the project 408 */ 409 private Project getProject() 410 { 411 return m_project; 412 } 413 414 /** 415 * Convert a urn to a url wrapping any errors in a build exception. 416 * @param urn the urn 417 * @return the uri 418 * @exception BuildException if a convertion error occurs 419 */ 420 private URI convertToURI( String urn ) throws BuildException 421 { 422 try 423 { 424 return new URI( urn ); 425 } 426 catch( Exception e ) 427 { 428 final String error = 429 "Unable to convert the urn [" 430 + urn 431 + "] to a uri."; 432 throw new BuildException( error, e ); 433 } 434 } 435 436 /** 437 * Notification that the build has started. 438 * @param event the build event 439 * @exception BuildException if a build error occurs 440 */ 441 public void buildStarted( BuildEvent event ) 442 throws BuildException 443 { 444 initialize( event.getProject(), false ); 445 } 446 447 /** 448 * Notification that a sub build has started. 449 * @param event the build event 450 */ 451 public void subBuildStarted( BuildEvent event ) 452 { 453 initialize( event.getProject(), true ); 454 } 455 456 /** 457 * Notification that a sub build has finished. 458 * @param event the build event 459 */ 460 public void subBuildFinished( BuildEvent event ) 461 { 462 } 463 464 /** 465 * Notification that the build has finished. 466 * @param event the build event 467 */ 468 public void buildFinished( BuildEvent event ) 469 { 470 } 471 472 /** 473 * Notification that the build target has started. 474 * @param event the build event 475 */ 476 public void targetStarted( BuildEvent event ) 477 { 478 } 479 480 /** 481 * Notification that the build target has finished. 482 * @param event the build event 483 */ 484 public void targetFinished( BuildEvent event ) 485 { 486 } 487 488 /** 489 * Notification that the build task has started. 490 * @param event the build event 491 */ 492 public void taskStarted( BuildEvent event ) 493 { 494 } 495 496 /** 497 * Notification that the build task has finaished. 498 * @param event the build event 499 */ 500 public void taskFinished( BuildEvent event ) 501 { 502 } 503 504 /** 505 * Notification of a message logged. 506 * @param event the build event 507 */ 508 public void messageLogged( BuildEvent event ) 509 { 510 } 511 } 512